.. _глава_29_start:

Цикл foreach
============

.. index::
    foreach
    for

Одним из наиболее распространенных операторов в D является цикл foreach. Он используется для
применения одних и тех же операций к каждому элементу контейнера (или диапазона).
Операции, которые применяются к элементам контейнеров, очень распространены в
программировании. Мы видели в главе о цикле for (:ref:`цикл for<цикл for>`), что
доступ к элементам массива в цикле for осуществляется по значению индекса, которое увеличивается на каждой
итерации:

.. code-block:: d

    for (int i = 0; i != array.length; ++i)
    {
        writeln(array[i]);
    }

Следующие шаги связаны с итерацией по всем элементам:

* Определение переменной в качестве счетчика, которая обычно называется i
* Повторением цикла до значения счетчика равного размеру массива (определяется через свойство массива length)
* Увеличение счётчика
* Доступ к элементу

foreach имеет, по сути, такое же поведение, но он упрощает код, обрабатывая
эти шаги автоматически

.. code-block:: d

    foreach (element; array)
    {
        writeln(element);
    }

Часть преимуществ for each заключается в том, что его можно использовать
независимо от типа контейнера. Как мы видели в предыдущей
главе, одним из способов перебора значений ассоциативного массива в цикле for
является первый вызов свойства array .values:

.. code-block:: d

    auto values = aa.values;
    for (int i = 0; i != values.length; ++i)
    {
        writeln(values[i]);
    }

foreach не требует ничего особенного для ассоциативных массивов; он используется точно
так же, как и с массивами:

.. code-block:: d

    foreach (value; aa)
    {
        writeln(value);
    }

Синтаксис foreach
-----------------

Синтаксис foreach можно разделить на 3 части

.. code-block:: d

    foreach (names; container_or_range)
    {
        operations
    }

* container_or_range указывает, на хранилище (или диапазон), где находятся элементы.
* operations указывает на операции, применяемые к каждому элементу.
* names указывает имя элемента и, возможно, другие переменные
  в зависимости от типа контейнера или диапазона. Хотя выбор
  имен остается за программистом, количество и типы этих имен
  зависят от типа контейнера.


Операторы continue and break
------------------------------

Эти ключевые слова имеют то же значение, что и для цикла for: continue
переходит к следующей итерации до завершения остальных операций с
текущим элементом, а break полностью завершает цикл.

Цикл foreach с массивами
-------------------------

При использовании foreach с простыми массивами и наличии единственного имени, указанного в разделе
names, это имя представляет значение элемента на каждой итерации:

.. code-block:: d

    foreach (element; array)
    {
        writeln(element);
    }

Когда в разделе имена указаны два имени, они представляют автоматический
счетчик и значение элемента соответственно:

.. code-block:: d

    foreach (i, element; array)
    {
        writeln(i, ": ", element);
    }

Счетчик автоматически увеличивается с помощью foreach. Хотя он может называться
как угодно еще, i - очень распространенное название автоматического счетчика.

foreach with strings and std.range.stride
Since strings are arrays of characters, foreach works with strings the same way
as it does with arrays: A single name refers to the character, two names refer to
the counter and the character, respectively:

foreach со string и std.range.stride
------------------------------------

Поскольку строки представляют собой массивы символов, foreach работает со строками так
же, как и с массивами: одно имя относится к символу, два имени относятся к
сче

.. code-block:: d

    foreach (c; "hello")
    {
        writeln(c);
    }

    foreach (i, c; "hello")
    {
        writeln(i, ": ", c);
    }

Однако, будучи кодовыми единицами UTF, char и wchar повторяют кодовые единицы UTF, а не
Кодовые точки Unicode:

.. code-block:: d

    foreach (i, code; "abcçd")
    {
        writeln(i, ": ", code);
    }

Доступ к двум кодовым единицам UTF-8, составляющим ç, будет осуществляться как к отдельным элементам:

.. code-block:: console

    1: b
    2: c
    3:
    4: ?????
    5: d

Одним из способов перебора символов Unicode строк в цикле foreach является
функция stride из модуля std.range. stride представляет строку как контейнер,
состоящий из символов Unicode. Его вторым параметром является количество шагов,
которые он должен сделать, перемещаясь по символам:

.. code-block:: d

    import std.range;
    // ...
    foreach (c; stride("abcçd", 1))
    {
        writeln(c);
    }


Независимо от типа символа string, stride всегда представляет свои
элементы в виде символов Unicode:

.. code-block:: console

    a
    b
    c
    ç
    d

Ниже я объясню, почему этот цикл не мог включать автоматический счетчик.

foreach с ассоциативными массивами
----------------------------------

При использовании foreach с ассоциативными массивами одно имя относится к значению,
в то время как два имени относятся к ключу и значению соответственно:

.. code-block:: d

    foreach (value; aa)
    {
        writeln(value);
    }

    foreach (key, value; aa)
    {
        writeln(key, ": ", value);
    }

Ассоциативные массивы также могут предоставлять свои ключи и значения в виде диапазонов.
Диапазоны мы рассмотрим в следующей главе (:ref:`Диапазоны<глава_81_start>`). .by Key, .ByValue и .byKeyValue возвращают
эффективные объекты диапазона, которые также полезны в контекстах, отличных от циклов foreach.
.ByValue не приносит никакой пользы в циклах foreach по сравнению с обычной
итерацией значений, описанной выше. С другой стороны, .byKey - это единственный эффективный способ
перебора только ключей ассоциативного массива:

.. code-block:: d

    foreach (key; aa.byKey)
    {
        writeln(key);
    }

свойство .byKeyValue предоставляет каждый элемент key-value через переменную, аналогичную
кортежу (:ref:`Кортежи<глава_77_start>`). Доступ к ключу и значению осуществляется раздельно через свойства
.key и .value этой переменной:

.. code-block:: d

    foreach (element; aa.byKeyValue)
    {
        writefln("The value for key %s is %s",
        element.key, element.value);
    }

foreach с числовыми диапазонами
-------------------------------


Мы уже рассматривали диапазоны чисел ранее, в главе "Срезы и другие функции массива
" (:ref:`Фрагменты и прочие возможности массивов<Фрагменты и прочие возможности массивов>`). Можно указать диапазон чисел в разделе container_or_range:

.. code-block:: d

    foreach (number; 10..15)
    {
        writeln(number);
    }

Помним, что 10 будет включено в диапазон, но 15 - нет.

foreach со структурами, классами и диапазонами
----------------------------------------------

foreach также может использоваться с объектами пользовательских типов, которые определяют свою собственную
итерацию в циклах foreach. Структуры и классы обеспечивают поддержку итерации цикла foreach либо с помощью своих функций-членов opApply(), либо с помощью набора
функций-членов range. Мы рассмотрим эти возможности в последующих главах.

Счетчик работает автоматически только для массивов
--------------------------------------------------

Автоматический счетчик предоставляется только при переборе массивов.
Для других контейнеров есть два варианта

* Использование преимуществ std.range.enumerate, как мы увидим позже в главе
  foreach со структурами и классами (стр. 491).
* Явное определение и увеличение переменной счетчика:

.. code-block:: d

    size_t i = 0;
    foreach (element; container)
    {
        // ...
        ++i;
    }

Подобная переменная также необходима при подсчете определенного условия.
Например, следующий код подсчитывает только те значения, которые делятся на 10:

.. code-block:: d

    import std.stdio;

    void main()
    {
        auto numbers = [1, 0, 15, 10, 3, 5, 20, 30];
        size_t count = 0;
        foreach (number; numbers)
        {
            if ((number % 10) == 0)
            {
                ++count;
                write(count);
            }
            else
            {
                write(' ');
            }
            writeln(": ", number);
        }
    }


Вывод

.. code-block:: console

     : 1
    1: 0
     : 15
    2: 10
     : 3
     : 5
    3: 20
    4: 30

Копия элемента, а не сам элемент
--------------------------------

Цикл foreach обычно предоставляет копию элемента, а не фактический
элемент, который хранится в контейнере. Это может быть причиной ошибок.
Чтобы увидеть пример этого, давайте взглянем на следующую программу, которая
пытается удвоить значения элементов массива

.. code-block:: d

    import std.stdio;

    void main()
    {
        double[] numbers = [ 1.2, 3.4, 5.6 ];
        writefln("Before: %s", numbers);
        foreach (number; numbers) {
            number *= 2;
        }
        writefln("After : %s", numbers);
    }

Выходные данные программы указывают на то, что присвоение, выполненное каждому элементу
внутри тела foreach, не оказывает никакого влияния на элементы контейнера:

Вывод

.. code-block:: console

    Before: [1.2, 3.4, 5.6]
    After : [1.2, 3.4, 5.6]

Это происходит потому, что number - это не фактический элемент массива, а копия каждого
элемента. Когда необходимо работать с фактическими элементами, имя должно быть
определено как ссылка на фактический элемент с помощью ключевого слова ref:

.. code-block:: d

    foreach (ref number; numbers)
    {
        number *= 2;
    }

Новый вывод показывает, что программа работает как ожидалось изменяя фактические элементы
массива

.. code-block:: console

    Before: [1.2, 3.4, 5.6]
    After : [2.4, 6.8, 11.2]

Ключевое слово ref делает number псевдонимом фактического элемента на каждой итерации.
В результате модификации с помощью number изменяют этот фактический элемент
контейнера.

Целостность контейнера должна быть сохранена
--------------------------------------------

Хотя можно изменять элементы контейнера с помощью переменных ref,
структура контейнера не должна изменяться во время его итерации.
Например, элементы не должны удаляться или добавляться в контейнер во
время цикла foreach.
Такие модификации могут запутать внутреннюю работу итерации цикла и
привести к неправильным состояниям программы.


foreach_reverse для выполнения итерации в обратном направлении
--------------------------------------------------------------

foreach_reverse работает так же, как и для каждого, за исключением того, что он выполняет итерацию в
обратном направлении:

.. code-block:: d

    auto container = [ 1, 2, 3 ];
    foreach_reverse (element; container)
    {
        writefln("%s ", element);
    }

Вывод

.. code-block:: console

        3
        2
        1

Использование foreach_reverse не распространено, поскольку функция range retro()
достигает той же цели. Мы рассмотрим retro() в следующей главе.

Упражнение
----------

Мы знаем, что ассоциативные массивы обеспечивают отображение ключей в значения. Это
отображение является однонаправленным: доступ к значениям осуществляется с помощью ключей, но не наоборот.
Предположим, что уже существует следующий ассоциативный массив:

.. code-block:: d

    string[int] names = [ 1:"one", 7:"seven", 20:"twenty" ];

Используйте этот ассоциативный массив и цикл foreach для заполнения другого ассоциативного массива
с именем values. Новый ассоциативный массив должен содержать значения, соответствующие
именам. Например, в следующей строке должно быть напечатано 20:

.. code-block:: d

    writeln(values["twenty"]);

